﻿using RockBus.DataContracts;
using RockBus.Logging.Interfaces;
using RockBus.Transport.Interfaces;
using RockBus.Transport.Wcf;
using System;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using System.Xml;

namespace RockBus.ServiceBus
{
    public class ClientTransport
    {
        public ClientTransport(ILogger logger, EndpointInfo endpointInfo)
        {
            this.TransportFactory = new TransportFactory(logger);
            _lazyRockBusTransport = new Lazy<ITransport>(() => { return this.TransportFactory.CreateTransport(endpointInfo); }, true);
        }

        public TransportFactory TransportFactory { get; private set; }

        private readonly Lazy<ITransport> _lazyRockBusTransport;

        private ITransport Transport { get { return this._lazyRockBusTransport.Value; } }

        public MessageVersion MessageVersion { get { return this.Transport.MessageVersion; } }

        #region SendMessageAsync

        /// <summary>
        /// Sends a message to RockBus with the specified action and/or topic.
        ///  If the action/topic isn't specified (is null), will try to resolve the action/topic using previously registered RockBus Message Types (see the ScanXxx methods).
        /// </summary>
        /// <typeparam name="TRequest">The type of the body to send.</typeparam>
        /// <param name="body">The message body to send.</param>
        /// <param name="action">The SOAP action to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <param name="topic">The topic to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <returns></returns>
        public Task SendMessageAsync<TRequest>(TRequest body, string action = null, string topic = null)
        {
            var messageMetadata = RockBusMessageAttribute.ScanType(typeof(TRequest));
            var message = Message.CreateMessage(
                this.MessageVersion,
                action ?? messageMetadata.Action,
                body);
            return this.SendMessageAsync(message, topic ?? messageMetadata.DefaultTopic);
        }

        /// <summary>
        /// Sends a message to RockBus with the specified action and/or topic. The action is required.
        ///  If the topic isn't specified (is null), will try to resolve the topic using previously registered RockBus Message Types (see the ScanXxx methods).
        /// </summary>
        /// <param name="reader">The XmlReader, pointing to the message body.</param>
        /// <param name="action">The SOAP action to associate to the message. Required.</param>
        /// <param name="topic">The topic to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <returns></returns>
        public Task SendMessageAsync(XmlReader reader, string action, string topic = null)
        {
            var message = Message.CreateMessage(
                this.MessageVersion,
                action,
                reader);
            return this.SendMessageAsync(message, topic);
        }

        /// <summary>
        /// Sends a message to RockBus with the specified action and/or topic.
        ///  If the topic isn't specified (is null), will try to resolve the topic using previously registered RockBus Message Types (see the ScanXxx methods).
        /// </summary>
        /// <param name="message">The message to send</param>
        /// <param name="topic">The topic to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <returns></returns>
        public Task SendMessageAsync(Message message, string topic = null)
        {
            return this.SendMessageInternalAsync(message, topic);
        }

        #endregion SendMessageAsync

        #region SendRequestAsync

        /// <summary>
        /// Sends a request to RockBus with the specified action and/or topic and returns a response.
        ///  If the action/topic isn't specified (is null), will try to resolve the action/topic using previously registered RockBus Message Types (see the ScanXxx methods).
        /// </summary>
        /// <typeparam name="TRequest">The type of the request body.</typeparam>
        /// <typeparam name="TResponse">The type of the response body.</typeparam>
        /// <param name="body">The request body to send.</param>
        /// <param name="action">The SOAP action to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <param name="topic">The topic to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <returns></returns>
        public async Task<TResponse> SendRequestAsync<TRequest, TResponse>(TRequest body, string action = null, string topic = null)
        {
            var messageMetadata = RockBusMessageAttribute.ScanType(typeof(TRequest));
            var requestMessage = Message.CreateMessage(
                this.Transport.MessageVersion,
                action ?? messageMetadata.Action,
                body);

            var responseMessage = await this.SendRequestAsync(requestMessage, topic ?? messageMetadata.DefaultTopic);

            var response = responseMessage.GetBody<TResponse>();

            return response;
        }

        /// <summary>
        /// Sends a request to RockBus with the specified action and/or topic and returns a response. The action is required.
        ///  If the topic isn't specified (is null), will try to resolve the topic using previously registered RockBus Message Types (see the ScanXxx methods).
        /// </summary>
        /// <param name="reader">The XmlReader, pointing to the request body.</param>
        /// <param name="action">The SOAP action to associate to the message. Required.</param>
        /// <param name="topic">The topic to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <returns></returns>
        public async Task<XmlReader> SendRequestAsync(XmlReader reader, string action, string topic = null)
        {
            var requestMessage = Message.CreateMessage(
                this.Transport.MessageVersion,
                action,
                reader);

            var responseMessage = await this.SendRequestAsync(requestMessage, topic);

            return responseMessage.GetReaderAtBodyContents();
        }

        /// <summary>
        /// Sends a request to RockBus with the specified action and/or topic and returns a response.
        ///  If the topic isn't specified (is null), will try to resolve the topic using previously registered RockBus Message Types (see the ScanXxx methods).
        /// </summary>
        /// <param name="message">The message to send</param>
        /// <param name="topic">The topic to associate to the message. If null, will try to resolve it using previously registered RockBus Message Types (see the ScanXxx methods).</param>
        /// <returns></returns>
        public Task<Message> SendRequestAsync(Message message, string topic = null)
        {
            return this.SendMessageInternalAsync(message, topic);
        }

        #endregion SendRequestAsync

        private Task<Message> SendMessageInternalAsync(Message message, string topic = null)
        {
            if (!string.IsNullOrEmpty(topic))
            {
                TopicHeader topicHeader = new TopicHeader(new TopicInfo() { Name = topic });
                MessageHeader msgHeader = MessageHeader.CreateHeader(
                    TopicHeader.HeaderName,
                    StringConstants.RockBusNamespace,
                    topicHeader);
                message.Headers.Add(msgHeader);
            }

            return this.Transport.StartTransportMessage(message);
        }
    }
}